package academic.emailClient;


import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.UUID;
import java.util.logging.Logger;


import academic.emailClient.control.EmailClientController;
import academic.emailClient.model.EmailClientModelImp;
import academic.emailClient.model.EmailClientModel;
import academic.emailClient.model.entities.Meeting;
import academic.emailClient.model.entities.Message;
import academic.emailClient.security.model.User;
import academic.emailClient.security.persistence.UserDAOMock;
import academic.emailClient.view.EmailClientViewImp;
import academic.emailClient.view.EmailClientViewInterface;

/** 
 *  Implements Email Client Class, that is responsible for instantiating model, view and controller and 
 *  connects to server to send or update information
 */

public class EmailClientImpl implements EmailClient{

	private ObjectInputStream in;
	private ObjectOutputStream out;
	private Socket server;
	private EmailClientModel model;
	private EmailClientViewInterface view;
	private EmailClientController controller;
	private String serverHost;
	private int serverPort;
	private String emailAccount;
	static Logger logger = Logger.getLogger(EmailClientImpl.class.getName());
	private User connectedUser;


	/**
	 * 
	 * @param serverHost
	 * @param serverPort
	 * @param emailAccount
	 * @throws Exception
	 */
	public EmailClientImpl(String serverHost, int serverPort, String emailAccount) throws Exception{
		super();
		this.serverHost = serverHost;
		this.serverPort = serverPort;
		this.emailAccount = emailAccount;
		
		connectedUser = new UserDAOMock().getUser(emailAccount);
		model = new EmailClientModelImp(emailAccount, this);
		view = new EmailClientViewImp(connectedUser);
		controller = new EmailClientController(view,model);
		model.runFiltersExecution();
	}

	@Override
	public void start(){
		ListenFromServer listener;

		//Starts connection with server
		try {
			logger.info("User " +  connectedUser.getFullname() + " " + emailAccount );
			logger.info("Attempting to connect to server...");
			logger.info("Host: " + serverHost + " Port: " + serverPort); 
			server = new Socket(serverHost, serverPort);

			logger.info("Connection Accepted.....");

			//Initializes communication streams			
			out = new ObjectOutputStream(server.getOutputStream());
			in = new ObjectInputStream(server.getInputStream());

			//Writes email account of the client that is connected
			out.writeObject(emailAccount);

			//Starts listener from the server
			listener = new ListenFromServer();
			listener.start();

		}
		catch (IOException e){
			logger.severe("Unable to connect to server");
			logger.severe(e.toString());
			disconect();
			return;
		}

	}

	/**
	 * Disconnects client from the server
	 */
	private void disconect(){
		try { in.close(); } catch (Exception e){}
		try { out.close(); } catch (Exception e){}
		try { server.close(); } catch (Exception e){}
	}

	@Override
	public synchronized void sendMessage(Message message){
		try {
			logger.info("Sending message to server");
			out.writeObject(CommunicationInstruction.SEND_MESSAGE.value());
			out.writeObject(message);
		}
		catch (Exception ioe){
			logger.severe(ioe.toString());
		}
	}

	@Override
	public synchronized void sendMeeting(Meeting meeting){
		try {
			logger.info("Sending meeting to server");
			out.writeObject(CommunicationInstruction.CREATE_MEETING.value());
			out.writeObject(meeting);
		}
		catch (Exception ioe){
			logger.severe(ioe.toString());
		}
	}

	@Override
	public void declineMeeting(UUID meetingID) {
		try {
			logger.info(emailAccount +  " is declining meeting " + meetingID.toString());
			out.writeObject(CommunicationInstruction.DECLINE_MEETING.value());
			out.writeObject(meetingID);
			out.writeObject(emailAccount);
		}
		catch (Exception ioe){
			logger.severe(ioe.toString());
			ioe.printStackTrace();
		}		
	}

	@Override
	public synchronized void sendMeetingCancellation(UUID meetingID){
		try {
			logger.info("Sending meeting cancellation:" + meetingID.toString());
			out.writeObject(CommunicationInstruction.CANCEL_MEETING.value());
			out.writeObject(meetingID);
		}
		catch (Exception ioe){
			logger.severe(ioe.toString());
			ioe.printStackTrace();
		}

	}
	
	@Override
	public synchronized void requestUpdateMeeting(Meeting meeting){
		try {
			logger.info("Sending request to update meeting: " + meeting.getId().toString());
			out.writeObject(CommunicationInstruction.REQUEST_UPDATE_MEETING.value());
			out.writeObject(meeting);
		}
		catch (Exception ioe){
			logger.severe(ioe.toString());
			ioe.printStackTrace();		
		}
	}

	/**
	 * Main method
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String args[]) throws Exception{
		String emailAccount = args[0];
		String serverHost = args[1];
		int serverPort = Integer.parseInt(args[2]);
		EmailClientImpl client = new EmailClientImpl(serverHost, serverPort, emailAccount);
		client.start();
	}


	/**
	 * Class that implements the listener of the instructions given by the server
	 * @author Alejandra
	 *
	 */
	class ListenFromServer extends Thread{

		/**
		 * Listens instructions given by the server and attends them
		 */
		public void run(){		
			try {
				Integer instructionCode = null;
				CommunicationInstruction instruction; 
				while ((instructionCode = (Integer) in.readObject()) != null){
					instruction = CommunicationInstruction.convert(instructionCode);
					logger.info("instruction: " + instruction.name());				
					//Waits for the instruction given by the server
					switch (instruction) {				
					//Receive a message
					case RECEIVE_MESSAGE:
						Message message = (Message) in.readObject();
						logger.info("Message Received: " + message.getId().toString());
						model.receiveMessage(message);
						break;
					case CREATE_MEETING:
						Meeting meeting = (Meeting) in.readObject();
						logger.info("Meeting Received: " + meeting.getId().toString());
						model.receiveMeeting(meeting);
						break;
					case RECEIVE_MEETING_DECLINATION:
						UUID meetingID = (UUID) in.readObject();
						String decliner = (String) in.readObject();
						model.receiveMeetingDeclination(meetingID, decliner);
						break;
					case CANCEL_MEETING:
						UUID toCancelMeetingID = (UUID) in.readObject();
						model.receiveMeetingCancellation(toCancelMeetingID);
						break;
					case CONFIRM_MEETING_UPDATE:
						Meeting updatedMeeting = (Meeting) in.readObject();
						model.confirmMeetingUpdate(updatedMeeting);
						view.loadModifyMeetingAreas(new ArrayList<>(model.getMeetings().values()));
						view.displaySuccessfulMeetingUpdateMessage();
						break;
					case RESOLVE_MEETING_CONFLICTS:
						Meeting previosMeeting = (Meeting) in.readObject();
						Meeting meetingWithConflicts = (Meeting) in.readObject();
						ArrayList<String> conflicts = (ArrayList<String>) in.readObject();
						view.showMergeMeetingForm(meetingWithConflicts, previosMeeting, conflicts);
						break;
					case RECEIVE_MEETING_UPDATE:
						Meeting changedMeeting = (Meeting) in.readObject();
						model.updateMeeting(changedMeeting);
					default:
						break;
					}
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
	}


}
